#include "feature_db.h"
#include <string>
#include <string.h>
#include <stdlib.h>
using namespace std;

feature_db::feature_db()
{
	_env = 0;
	_db = 0;
	_feat = 0;
	int ret = db_create(&_db, NULL, 0); 
	if ( ret != 0 ) {
		cerr << "db_create error" << endl;
		if ( _db ){
			_db->close(_db, 0);
			_db = NULL;
		}
	}
}

bool feature_db::open(const string &file, open_flag_t flags)
{
	_file = file;
	int db_flags = DB_RDONLY;
	if (flags == read)
		db_flags = DB_RDONLY;
	if (flags == create)
		db_flags = DB_CREATE;

	if (_db->open(_db, NULL, _file.c_str(), NULL, DB_HASH, db_flags | DB_THREAD, 0)) {
		_db->close(_db, 0);
		_db = NULL;
		return false;
	}
	return true;
}

bool feature_db::open(const string &file, const string &feat_type, open_flag_t flags)
{
	_file = file;

	int ret = -1;
	if (feat_type == "color")
		_feat = new feature<color_feat_data_t>;
	else if (feat_type == "shape")
		_feat = new feature<shape_feat_data_t>;
	else if (feat_type == "sift")
		_feat = new feature<sift_feat_data_t>;
	else if (feat_type == "topology")
		_feat = new feature<topo_feat_data_t>;
	else if (feat_type == "global_feat")
		_feat = new feature<global_feat_data_t>;
	else if (feat_type == "local_feat")
		_feat = new feature<local_feat_data_t>;
	else return false;

	int db_flags = DB_RDONLY;
	if (flags == read)
		db_flags = DB_RDONLY;
	if (flags == create)
		db_flags = DB_CREATE;

	if (_db->open(_db, NULL, _file.c_str(), NULL, DB_HASH, db_flags, 0)) {	
		cerr << "db open error" << endl;
		_db = 0;
		return false;
	}

	cout << "db opened: " << file << ", " << feat_type << endl;

	return true;
};

int feature_db::mem_open(const string &db_001, const string &name, const string &feat_type)
{
	DB_ENV *mem_env;
	int ret = -1;

	if (feat_type == "color")
		_feat = new feature<color_feat_data_t>;
	else if (feat_type == "shape")
		_feat = new feature<shape_feat_data_t>;
	else if (feat_type == "sift")
		_feat = new feature<sift_feat_data_t>;
	else if (feat_type == "topology")
		_feat = new feature<topo_feat_data_t>;
	else if (feat_type == "global_feat")
		_feat = new feature<global_feat_data_t>;
	else if (feat_type == "local_feat")
		_feat = new feature<local_feat_data_t>;
	else return false;

	
	if (db_001 != "") {
		string cmd = std::string("rm -f ./__db.001; ln -s ") + db_001 + " ./__db.001 &> /dev/null";
		if (system(cmd.c_str()) < 0) {
			printf("command failed : %s\n", cmd.c_str());
			return -1;
		}
	}

	if (ret = db_env_create(&mem_env, 0)) {
		printf( "db_env_create error, %s", db_strerror(ret));
		return ret;
	}

/*
	if (ret = mem_env->set_shm_key(mem_env, shm_key)) {
		printf( "set_shm_key error, %s", db_strerror(ret));
		return ret;
	}

	unsigned int gbytes = cachesize / (1024*1024*1024),
		     bytes = cachesize % (1024*1024*1024);

	if (ret = mem_env->set_cachesize(mem_env, gbytes, bytes, 1)) {
		printf( "set_cachesize error, cachesize: %llu, %s", cachesize, db_strerror(ret));
		return ret;
	}
*/
	if (ret = mem_env->log_set_config(mem_env, DB_LOG_IN_MEMORY, 1)) {
		printf( "set log in mem failed, %s", db_strerror(ret));
		return ret;
	}

	unsigned int env_flags =
		DB_RDONLY     |
		DB_INIT_LOCK  |
		DB_INIT_LOG   |
		DB_INIT_TXN   |
		DB_INIT_MPOOL |
		DB_THREAD     |
		DB_SYSTEM_MEM;
	for (int i = 0; i != 10; i++) {
		ret = mem_env->open(mem_env, NULL, env_flags, 0);
		if (ret != 0) {
			// wait for system(cmd) in other matcher finished
			usleep(10000);
			continue;
		}
		break;
	}
	if (ret != 0) {
		printf("DB_ENV->open error: %s", db_strerror(ret));
		return ret;
	}
	
	
	_env = mem_env;

	DB *mem_db;
	if (ret = db_create(&mem_db, _env, 0)) {
		printf("db_create error,%s", db_strerror(ret));
		return ret;
	}

	if (ret = mem_db->open(mem_db, 0, 0, name.c_str(), DB_BTREE, DB_RDONLY | DB_THREAD, 0)) {
		printf("db->open error, %s", db_strerror(ret));
		return ret;
	}

	_db = mem_db;
	return 0;
}

int feature_db::set_cachesize(long long size)
{
	if (!_db) 
		return -1;
	int gbytes = size/ (1024 * 1024 * 1024);
	int bytes = size% (1024 * 1024 * 1024);
	return _db->set_cachesize(_db, gbytes, bytes, 0);
}	
int feature_db::put(int subid, int vid, const vector<base_feature*> &feat_vec)
{
	if (feat_vec.empty())
		return false;
	struct db_key
	{
		int subid;
		int vid;
	} key_data;
	key_data.vid = vid;
	key_data.subid = subid;

	int featlen = feat_vec[0]->get_length();
	int buflen = featlen * feat_vec.size();
	unsigned char *buf = new unsigned char[buflen];
	for (int i = 0; i != feat_vec.size(); i++)
		memcpy(buf + featlen * i, feat_vec[i]->get_data_buf(), feat_vec[i]->get_length());

	//cout << "feature put : <" << vid << ", " ;
	//for (int i = 0; i != feat_vec.size() && i != 4; i++)
	//	cout << feat_vec[i]->get_iid() << " ";
	//cout << ">" << endl;

	DBT key, data;
	memset(&key, 0, sizeof(DBT));
	memset(&data, 0, sizeof(DBT));
	key.size = sizeof(db_key);
	key.data = &key_data;

	data.size = buflen;
	data.data = buf;
	data.flags = DB_DBT_MALLOC;

	int ret;
	if (ret = _db->put(_db, 0, &key, &data, DB_NOOVERWRITE)) {
		_db->err(_db, ret, "db put errror");
	}
	delete buf;
	return ret;
}

int feature_db::append(int subid, int vid, base_feature *feat)
{
	int db_ret;
	vector<base_feature *>feat_vec;
	if (get(subid, vid, feat_vec) == 0 && (db_ret = del(subid, vid)) != 0) {
			_db->err(_db, db_ret, "db del error");
			return db_ret;
	}
	feat_vec.push_back(feat);
	db_ret = put(subid, vid, feat_vec);
	if (feat_vec.size() > 1) {
		for (int i = 0; i != feat_vec.size() - 1; i++)
			delete feat_vec[i];
	}
	return db_ret;
};

int feature_db::del(int subid, int vid)
{
	struct db_key {
		int subid;
		int vid;
	} dk;
	dk.subid = subid;
	dk.vid = vid;
	
	DBT k;
	memset(&k, 0, sizeof(k));
	k.size = sizeof(db_key);
	k.data = &dk;
	return _db->del(_db, 0, &k, 0);
}

int feature_db::get(int subid, int vid, vector<base_feature *> &feat_vec)
{
	struct db_key
	{
		int subid;
		int vid;
	} key_data;
	key_data.vid = vid;
	key_data.subid = subid;
	DBT key, data;
	memset(&key, 0, sizeof(DBT));
	memset(&data, 0, sizeof(DBT));
	data.flags = DB_DBT_MALLOC;
	key.size = sizeof(db_key);
	key.data = &key_data;

	int ret;
	if (ret = _db->get(_db, 0, &key, &data, 0) != 0) {
		return ret;
	}
	
	int num_features = data.size / _feat->get_length();
	for (int i = 0; i != num_features; i++) {
		memcpy(_feat->get_data_buf(), (char *)data.data + _feat->get_length() * i, _feat->get_length());
		feat_vec.push_back(_feat->clone());
	}
	return 0;
};

int feature_db::put_word_count(long long image_count, int subid)
{
	char skey[64];
	DBT key,data;
	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));
	snprintf(skey, 64, "TotalWordCount_%d", subid);
	key.size = strlen(skey) + 1;
	key.data = skey;
	long long word_count = image_count / 12;
	data.size = sizeof(long long);
	data.data = &word_count;
	int ret ;
	if (ret = _db->put(_db, 0, &key, &data, DB_NOOVERWRITE)) {
		_db->err(_db, ret, "db put errror");
		return -1;
	}
	return 0;
}

int feature_db::traverse(traverse_callback on_traverse, void *arg)
{
	DBC *db_cursor;
	int db_ret;
	if (db_ret = _db->cursor(_db, NULL, &db_cursor, 0) != 0)
		return -1;
	DBT key, data;
	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));

	while ((db_ret = db_cursor->get(db_cursor, &key, &data, DB_NEXT)) == 0) {
		if (on_traverse(key, data, arg) != 0)
			break;
		memset(&key, 0, sizeof(key));
		memset(&data, 0, sizeof(data));
	}
	db_cursor->close(db_cursor);
	if (db_ret != DB_NOTFOUND)
		return -1;
	return 0;
}

int feature_db::traverse_class(int subid, int vid_start, int vid_end)
{
	struct key_data {
		int subid;
		int vid;
	} key_data;

	DBT key, data;
	for (int vid = vid_start; vid != vid_end + 1; vid++) {
		memset(&key, 0, sizeof(DBT));
		memset(&data, 0, sizeof(DBT));
		key_data.subid = subid;
		key_data.vid = vid;
		key.size = 8;
		key.data = &key_data;
		data.flags = DB_DBT_MALLOC;
		if (_db->get(_db, 0, &key, &data, 0) == 0)
			cout << data.size << " " << vid << " " << subid << endl;
	}
	return 0;
}

int feature_db::truncate_data(int toggle_size, int percents, feature_db &out_db)
{
	DBC *db_cursor;
	int db_ret;
	if (db_ret = _db->cursor(_db, NULL, &db_cursor, 0) != 0)
		return -1;
	struct db_key_t {
		int subid;
		int vid;
	} *kd;
	DBT key, data;
	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));
	int num_trunc_datas = 0;

	while ((db_ret = db_cursor->get(db_cursor, &key, &data, DB_NEXT)) == 0) {
		if (key.size != 8) {
			out_db.put(key, data);
		} else {
			if (data.size > toggle_size) {
				num_trunc_datas++;
				kd = static_cast<db_key_t *>(key.data);
				int old_data_size = data.size;
				int new_data_size = toggle_size - toggle_size % get_feature_length();
				if (new_data_size == 0) 
					new_data_size += get_feature_length();
				data.size = new_data_size;
				if ((db_ret = out_db.put(key, data)) != 0) {
					cout << "db data out put error " << endl;
					break;
				}
				cout << "data of <" << kd->subid << "," << kd->vid << "> truncated from " 
				<< old_data_size  << " bytes to " << new_data_size << endl;
			} else {
				if (db_ret = out_db.put(key, data) != 0) {
					cout << "db out put error " << endl;
					break;
				}
			}
			
		}
		memset(&key, 0, sizeof(key));
		memset(&data, 0, sizeof(data));
	}
	
	cout << num_trunc_datas << " data trucated " << endl;
	db_cursor->close(db_cursor);
	if (db_ret != DB_NOTFOUND)
		return -1;
	return 0;
}

int feature_db::get_vid_data(int vid, unsigned char *&out_data, unsigned long long &data_length)
{
	struct db_key
	{
		int vid;
	} key_data;
	key_data.vid = vid;
	DBT key, data;
	memset(&key, 0, sizeof(DBT));
	memset(&data, 0, sizeof(DBT));
	data.flags = DB_DBT_MALLOC;
	data.data = NULL;
	key.size = sizeof(db_key);
	key.data = &key_data;
	//key.flags = DB_DBT_USERMEM;

	int ret;
	if (ret = _db->get(_db, 0, &key, &data, 0) != 0) {
		data_length = 0;
		if (data.data){
			free(data.data);
		}
		return ret;
	}

	data_length = data.size;
	out_data = new unsigned char [data.size];
	memcpy(out_data, data.data, data.size);
	free(data.data);
	return 0;
}

int feature_db::get_total_word_count()
{
	return 0x7FFFFFFF / 16;
}
